package com.xyzboom.compiler.fa

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer
import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers
import com.fasterxml.jackson.databind.ser.std.StdKeySerializers
import com.fasterxml.jackson.module.kotlin.jsonMapper
import com.xyzboom.compiler.NoArg
import com.xyzboom.compiler.fa.EdgeType.*
import mu.KotlinLogging
import java.io.StringWriter
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.javaField

@JsonDeserialize(keyUsing = FAEdge.Companion.FAEdgeKeyDeserializer::class)
@JsonSerialize(keyUsing = FAEdge.Companion.FAEdgeKeySerializer::class)
@NoArg
class FAEdge<K> {
    @JsonProperty("v")
    val value: K

    @JsonProperty("t")
    val type: EdgeType

    constructor(value: K, type: EdgeType) {
        this.value = value
        this.type = type
    }

    constructor(value: K) : this(value, NORMAL)

    companion object {
        private const val typeKFieldName = "@K-Class"

        class FAEdgeKeyDeserializer : KeyDeserializer() {
            override fun deserializeKey(key: String, ctxt: DeserializationContext): Any? {
                val jsonMapper = jsonMapper()
                val map = jsonMapper.readerFor(
                    object : TypeReference<HashMap<String, Any>?>() {})
                    .readValue<HashMap<String, Any>?>(key) ?: return null
                val kClass = map[typeKFieldName]!!
                val value = FAEdge::class.java.constructors.first {
                    it.parameters.isEmpty()
                }.newInstance() as FAEdge<*>

                for (p in FAEdge::class.declaredMemberProperties) {
                    val jField = p.javaField!!
                    jField.isAccessible = true
                    val clazz: Class<*> = if (jField.genericType is Class<*>) {
                        jField.genericType as Class<*>
                    } else Class.forName(kClass.toString())
                    if (clazz.kotlin == Char::class) {
                        jField.set(
                            value,
                            (map[p.name] as String)[0]
                        )
                    } else {
                        jField.set(
                            value,
                            jsonMapper.readerFor(clazz).readValue(
                                jsonMapper.writeValueAsString(map[p.name])
                            )
                        )
                    }
                }
                return value
            }
        }

        class FAEdgeKeySerializer<K> : JsonSerializer<FAEdge<K>>() {
            override fun serialize(
                value: FAEdge<K>?, gen: JsonGenerator, serializers: SerializerProvider?,
            ) {
                if (value == null) {
                    gen.writeFieldName("null")
                    return
                }
                val jsonMapper = jsonMapper().apply {
                    activateDefaultTyping(polymorphicTypeValidator, DefaultTyping.NON_FINAL)
                }
                val sw = StringWriter()
                val jsonGen = JsonFactory().createGenerator(sw)
                jsonGen.writeStartObject()
                for (p in FAEdge::class.declaredMemberProperties) {
                    val fieldValue = p.get(value)
                    jsonGen.writeFieldName(p.name)
                    jsonMapper.writeValue(jsonGen, fieldValue)
                }
                jsonGen.writeFieldName(typeKFieldName)
                logger.trace { "java class name: ${value.value!!::class.java.name}" }
                jsonGen.writeString(value.value!!::class.java.name)
                jsonGen.writeEndObject()
                jsonGen.flush()
                gen.writeFieldName(sw.toString())
            }
        }

        val DOT_ANY_EDGE = FAEdge('.', DOT_ANY)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is FAEdge<*>) return false

        if (value != other.value) return false
        if (type != other.type) return false

        return true
    }

    override fun hashCode(): Int {
        var result = value?.hashCode() ?: 0
        result = 31 * result + type.hashCode()
        return result
    }

    override fun toString(): String {
        return when (type) {
            NORMAL -> value.toString()
            DOT_ANY -> "ANY"
            REVERSE -> "not $value"
            MOVE_IN -> "move in $value"
            REDUCE -> "reduce because $value"
            GOTO -> "goto because $value"
        }
    }

}

private val logger = KotlinLogging.logger {}
